package state

import (
	"fmt"

	"github.com/ethereum/go-ethereum/core"

	"github.com/ethereum-optimism/optimism/devnet-sdk/proofs/prestate"
	"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster"
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"

	"github.com/ethereum-optimism/optimism/op-chain-ops/addresses"
	"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
	"github.com/ethereum-optimism/optimism/op-service/eth"
	"github.com/ethereum-optimism/optimism/op-service/ioutil"
	"github.com/ethereum-optimism/optimism/op-service/jsonutil"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core/types"
)

// State contains the data needed to recreate the deployment
// as it progresses and once it is fully applied.
type State struct {
	// Version versions the state so we can update it later.
	Version int `json:"version"`

	// OpDeployerVersion is the version of op-deployer that was used to create the state
	OpDeployerVersion string `json:"opDeployerVersion"`

	// Create2Salt is the salt used for CREATE2 deployments.
	Create2Salt common.Hash `json:"create2Salt"`

	// AppliedIntent contains the chain intent that was last
	// successfully applied. It is diffed against new intent
	// in order to determine what deployment steps to take.
	// This field is nil for new deployments.
	AppliedIntent *Intent `json:"appliedIntent"`

	// PrestateManifest contains the prestate generated by the op-program-svc
	PrestateManifest *prestate.PrestateManifest `json:"prestateManifest"`

	// InteropDepSet contains the interop dependency set render by the prestate SDK if interop is enabled
	InteropDepSet *depset.StaticConfigDependencySet `json:"interopDepSet,omitempty"`

	// SuperchainDeployment contains the addresses of the Superchain
	// deployment. It only contains the proxies because the implementations
	// can be looked up on chain.
	SuperchainDeployment *addresses.SuperchainContracts `json:"superchainContracts"`

	// SuperchainRoles contains the addresses of the Superchain roles.
	SuperchainRoles *addresses.SuperchainRoles `json:"superchainRoles"`

	// ImplementationsDeployment contains the addresses of the common implementation
	// contracts required for the Superchain to function.
	ImplementationsDeployment *addresses.ImplementationsContracts `json:"implementationsDeployment"`

	// Chains contains data about L2 chain deployments.
	Chains []*ChainState `json:"opChainDeployments"`

	// L1StateDump contains the complete L1 state dump of the deployment.
	L1StateDump *GzipData[foundry.ForgeAllocs] `json:"l1StateDump"`

	// L1DevGenesis contains the dev L1 genesis, and may be nil if this is not a genesis-strategy deployment.
	// Warning: the Allocs part of the genesis is not included. Instead, the stateHash attribute is set.
	// The allocs are included in L1StateDump.
	// The stateHash can be used for consistency checks and faster block-hash computation.
	L1DevGenesis *core.Genesis `json:"-"`

	// DeploymentCalldata contains the calldata of each transaction in the deployment. This is only
	// populated if apply is called with --deployment-target=calldata.
	DeploymentCalldata []broadcaster.CalldataDump
}

func (s *State) WriteToFile(path string) error {
	return jsonutil.WriteJSON(s, ioutil.ToAtomicFile(path, 0o755))
}

func (s *State) Chain(id common.Hash) (*ChainState, error) {
	for _, chain := range s.Chains {
		if chain.ID == id {
			return chain, nil
		}
	}
	return nil, fmt.Errorf("chain not found: %s", id.Hex())
}

type AdditionalDisputeGameState struct {
	GameType      uint32
	GameAddress   common.Address
	VMAddress     common.Address
	OracleAddress common.Address
	VMType        VMType
}

type ChainState struct {
	ID common.Hash `json:"id"`

	addresses.OpChainContracts

	AdditionalDisputeGames []AdditionalDisputeGameState `json:"additionalDisputeGames"`

	Allocs *GzipData[foundry.ForgeAllocs] `json:"allocs"`

	StartBlock *L1BlockRefJSON `json:"startBlock"`
}

type L1BlockRefJSON struct {
	Hash       common.Hash    `json:"hash"`
	ParentHash common.Hash    `json:"parentHash"`
	Number     hexutil.Uint64 `json:"number"`
	Time       hexutil.Uint64 `json:"timestamp"`
}

func (b *L1BlockRefJSON) ToBlockRef() *eth.BlockRef {
	return &eth.BlockRef{
		Hash:       b.Hash,
		Number:     uint64(b.Number),
		ParentHash: b.ParentHash,
		Time:       uint64(b.Time),
	}
}

func BlockRefJsonFromBlockRef(br *eth.BlockRef) *L1BlockRefJSON {
	return &L1BlockRefJSON{
		Hash:       br.Hash,
		Number:     hexutil.Uint64(br.Number),
		ParentHash: br.ParentHash,
		Time:       hexutil.Uint64(br.Time),
	}
}

func BlockRefJsonFromHeader(h *types.Header) *L1BlockRefJSON {
	return BlockRefJsonFromBlockRef(eth.BlockRefFromHeader(h))
}
